﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Per_pixel_OCR
{
    class Forest
    {
        public Random rnd = new Random();
        public Pixel[] Inputs { get; private set; }
        public int NumTree { get; private set; }
        public int NumPixelsEachTree { get; private set; }
        DecisionTree[] Trees;

        public int TotalTrainPixels { get; private set; }
        public int TotalTestPixels { get; private set; }

        public int TotalTrainDigits { get; private set; }
        public int TotalTestDigits { get; private set; }

        public int TrainError { get; private set; }
        public double TrainAccuracy { get; private set; }

        public int TestError { get; private set; }
        public double TestAccuracy { get; private set; }

        public int TestErrorImage { get; private set; }
        public double TestAccuracyImage { get; private set; }

        public Forest(Digit[] trainDigits, Digit[] testDigits, Pixel[] trainPixels, Pixel[] testPixels, int numTree, int numPixelsEachTree)
        {
            this.Inputs = trainPixels;
            this.NumTree = numTree;
            this.NumPixelsEachTree = numPixelsEachTree;
            this.Trees = new DecisionTree[this.NumTree];

            for (int i = 0; i < this.NumTree; i++)
            {
                //Pixel[] randomSubset = GetRandomSubset(numPixelsEachTree);
                Pixel[] randomSubset = this.Inputs;
                this.Trees[i] = new DecisionTree(this, trainDigits, trainPixels, testDigits, testPixels, randomSubset, i);
                Console.WriteLine("Tree " + (i + 1) + "/" + this.NumTree + " is trained.");
            }

            Console.WriteLine("Training completed. Testing started...");

            this.TotalTrainDigits = trainDigits.Length;
            this.TotalTestDigits = testDigits.Length;

            this.TotalTrainPixels = trainPixels.Length;
            this.TotalTestPixels = testPixels.Length;

            this.TrainError = this.Test(trainDigits, trainPixels);
            this.TrainAccuracy = ((double)(this.TotalTrainPixels - this.TrainError) / this.TotalTrainPixels) * 100;

            this.TestError = this.Test(testDigits, testPixels);
            this.TestAccuracy = ((double)(this.TotalTestPixels - this.TestError) / this.TotalTestPixels) * 100;

            this.TestErrorImage = this.TestImageHistogram(testDigits, testPixels);
            this.TestAccuracyImage = ((double)(this.TotalTestDigits - this.TestErrorImage) / this.TotalTestDigits) * 100;

            FileOperations.WriteNewDatabase(trainDigits, Parameters.TrainOutputPath);
            FileOperations.WriteNewDatabase(testDigits, Parameters.TestOutputPath);

            Console.WriteLine("Log file is created to: " + this.Log());
        }

        public Pixel[] GetRandomSubset(int numRecords)
        {
            Pixel[] rVal = new Pixel[numRecords];

            for (int i = 0; i < rVal.Length; i++)
                rVal[i] = this.Inputs[rnd.Next(this.Inputs.Length)];

            return rVal;
        }

        public byte ComputeWithHistogram(Pixel pixel)
        {
            double[] accumulativeHistogram = new double[Parameters.HistogramLength];

            for (int i = 0; i < this.Trees.Length; i++)
            {
                double[] temp = this.Trees[i].ComputeWithHistogram(pixel);

                accumulativeHistogram.Add(temp);
            }

            int majority = accumulativeHistogram.MaxIndex();

            return (byte)majority;
        }

        public double[] ComputeHistogram(Pixel pixel)
        {
            double[] accumulativeHistogram = new double[Parameters.HistogramLength];

            for (int i = 0; i < this.Trees.Length; i++)
            {
                double[] temp = this.Trees[i].ComputeWithHistogram(pixel);

                accumulativeHistogram.Add(temp);
            }

            return accumulativeHistogram;
        }


        public int Test(Digit[] testDigits, Pixel[] testPixels)
        {
            int numTrue = 0;
            int numFalse = 0;

            for (int i = 0; i < testPixels.Length; i++)
            {
                testPixels[i].ClassID = this.ComputeWithHistogram(testPixels[i]);
                testPixels[i].ClassIDHistogram = this.ComputeHistogram(testPixels[i]);

                if (testPixels[i].ClassID == testPixels[i].Owner.DigitValue)
                    numTrue++;
                else
                    numFalse++;
            }   

            return numFalse;             
        }

        public int TestImageHistogram(Digit[] testDigits, Pixel[] testPixels)
        {
            int numTrue = 0;
            int numFalse = 0;
            
            for (int i = 0; i < testDigits.Length; i++)
            {
                double[] histogram = new double[Parameters.HistogramLength];

                for (int j = 0; j < testDigits[i].NonZeroPixels.Count; j++)
                {
                    double[] temp = testDigits[i].NonZeroPixels[j].ClassIDHistogram;
                    histogram.Add(temp);
                }

                byte result = (byte)histogram.MaxIndex();

                if (result == testDigits[i].DigitValue)
                {
                    numTrue++;
                    testDigits[i].DigitType = DigitType.TEST_SUCCESS;
                }
                else
                {
                    numFalse++;
                    testDigits[i].DigitType = DigitType.TEST_FAILED;
                }
            }

            return numFalse;
        }

        public string Log()
        {
            string path = FileOperations.GetPath();

            using (StreamWriter writer = File.CreateText(path))
            {
                writer.WriteLine("Date: " + DateTime.Now.ToString());
                writer.WriteLine("Forest total train digits: " + this.TotalTrainDigits);
                writer.WriteLine("Forest total test digits: " + this.TotalTestDigits);
                writer.WriteLine("Forest total train pixels: " + this.TotalTrainPixels);
                writer.WriteLine("Forest total test pixels: " + this.TotalTestPixels);
                writer.WriteLine("Forest number of trees: " + this.NumTree);
                writer.WriteLine("Forest number of pixels each tree: " + this.NumPixelsEachTree);
                writer.WriteLine("Forest train error (pixel) : " + this.TrainError + "/" + this.TotalTrainPixels  + " ( " + this.TrainAccuracy + "% Accuracy )");
                writer.WriteLine("Forest test error (pixel) : " + this.TestError + "/" + this.TotalTestPixels+ " ( " + this.TestAccuracy + "% Accuracy )");
                writer.WriteLine("Forest test error (image) : " + this.TestErrorImage + "/" + this.TotalTestDigits + " ( " + this.TestAccuracyImage + "% Accuracy )");
                writer.WriteLine();

                writer.WriteLine(Parameters.Log());

                for (int i = 0; i < this.NumTree; i++)
                {
                    writer.WriteLine(this.Trees[i].LogString);
                    writer.WriteLine();
                }

                writer.WriteLine();
            }

            return path;
        }
    }
}
